<?php

namespace tbollmeier\alteraparser;

use tbollmeier\alteraparser\syntaxgraph\Multiples;
use tbollmeier\alteraparser\syntaxgraph\Branches;
use tbollmeier\alteraparser\syntaxgraph\Vertex;
use tbollmeier\alteraparser\syntaxgraph as syntax;

class API
{

    /**
     * Set cardinality of element to [0..1]
     *
     * @param $element
     * @return Multiples
     */
    public static function optional($element)
    {
        return new Multiples($element, 0, 1);
    }

    /**
     * Set cardinality of element to [0..*]
     *
     * @param $element
     * @return Multiples
     */
    public static function many($element)
    {
        return new Multiples($element);
    }

    /**
     * Set cardinality of element to [1..*]
     *
     * @param $element
     * @return Multiples
     */
    public static function oneToMany($element)
    {
        return new Multiples($element, 1);
    }

    /**
     * Set cardinality of element to [n..m]
     *
     * @param $element
     * @return Multiples
     */
    public static function nToM($element, $n, $m)
    {
        return new Multiples($element, $n, $m);
    }

    /**
     * Create alternatives from the given branch arrays
     *
     * @param $branch, $branch2, ...
     * @return Branches
     */
    public static function fork()
    {
        $res = new Branches();

        $branches = func_get_args();
        foreach ($branches as $branch) {
            $branch = is_array($branch) ? $branch : [$branch];
            $res->addBranch($branch);
        }

        return $res;
    }

    /**
     * Create a sequence separated by $separator.
     * The elements are passed as further variadic arguments.
     *
     * @param $separator
     * @param $element1, $element2, ...
     * @return Branches
     */
    public static function sequence($separator)
    {
        $res = new Branches();
        $args = func_get_args();
        $numArgs = count($args);
        $elements = [];

        for ($i=1; $i<$numArgs; $i++) {
            if ($i > 1) {
                array_push($elements, $separator);
            }
            array_push($elements, $args[$i]);
        }

        $res->addBranch($elements);

        return $res;
    }

    /**
     * Create a grammar rule
     *
     * @param $name
     * @param $branches
     * @param null $transformAstFn
     * @return mixed
     */
    public static function grammar($name, $branches, $transformAstFn=null)
    {
        $res = call_user_func_array(['API', 'fork'], $branches)->setName($name);
        if ($transformAstFn) {
            $res->transformAst($transformAstFn);
        }
        $res->connect(new Vertex(syntax\VertexCategory::END));

        return $res;
    }

    /**
     * Create matcher element for a single character
     *
     * @param $ch
     * @return syntax\MatcherVertex
     */
    public static function singleChar($ch)
    {
        return new syntax\MatcherVertex([$ch]);
    }

    /**
     * Create matcher element for a range of characters
     *
     * @param $from
     * @param $to
     * @return syntax\MatcherVertex
     */
    public static function charRange($from, $to)
    {
        $chars = [];
        $iMax = ord($to);

        for ($i=ord($from); $i <= $iMax; $i++) {
            array_push($chars, chr($i));
        }

        return new syntax\MatcherVertex($chars);
    }

    /**
     * Create matcher element
     *
     * @param $element1, $element2, ...
     * @return syntax\MatcherVertex
     */
    public static function characters() {
        return new syntax\MatcherVertex(func_get_args());
    }

    /**
     * Create grammar element that matches a keyword
     *
     * @param $kw
     * @param bool $caseSensitive
     * @param string $name
     * @return Branches
     */
    public static function keyword($kw, $caseSensitive=true, $name='key')
    {
        $branch = [];
        $chars = str_split($kw);

        foreach ($chars as $ch) {
            if ($caseSensitive) {
                array_push($branch, self::singleChar($ch));
            } else {
                $element = self::fork(
                    self::singleChar(strtolower($ch)),
                    self::singleChar(strtoupper($ch)));
                array_push($branch, $element);
            }
        }

        return self::fork($branch)->setName($name);

    }

    /**
     * Create a token
     *
     * @param $element
     * @param string $name
     * @return static
     */
    public static function token($element, $name='token')
    {
        return self::fork($element)->setName($name);
    }

}
